//
// Created by amon.huang on 2021/7/25.
//

#include "FFMpeg.h"
#include "MusicConstDefine.h"

FFMpeg::FFMpeg(JNICall *pJniCall) : FFMpeg(pJniCall, "") {
}

FFMpeg::FFMpeg(JNICall *pJniCall, const char *url) : mutex() {
    this->pJniCall = pJniCall;
    this->url = static_cast<char *>(malloc(strlen(url) + 1));
    strcpy(this->url, url);
    readThreadT = 0;
    pthread_mutex_init(&mutex, nullptr);
    pPlayerStatus = new PlayerStatus();
}

FFMpeg::~FFMpeg() {
    release();
}

void FFMpeg::setUrl(const char *pUrl) {
    if (this->url) {
        free(this->url);
    }
    this->url = static_cast<char *>(malloc(strlen(pUrl) + 1));
    strcpy(this->url, url);
}

void *threadReadPacket(void *context) {
    auto *pFFMpeg = (FFMpeg *) context;
    Audio *pAudio = pFFMpeg->pAudio;
    Video *pVideo = pFFMpeg->pVideo;
    while (pAudio &&
           pFFMpeg->pPlayerStatus != nullptr &&
           !pFFMpeg->pPlayerStatus->isExit) {
        AVPacket *pPacket = av_packet_alloc();

        pthread_mutex_lock(&pAudio->seekMutex);
        int readFrameRes = av_read_frame(pFFMpeg->pFormatContext, pPacket);
        pthread_mutex_unlock(&pAudio->seekMutex);

        if (readFrameRes >= 0) {
            if (pPacket->stream_index == pAudio->streamIndex) {
                pAudio->pPackQueue->push(pPacket);
            } else if (pVideo &&
                       pPacket->stream_index == pVideo->streamIndex) {
                pVideo->pPackQueue->push(pPacket);
            } else {
                av_packet_unref(pPacket);
                av_packet_free(&pPacket);
            }
        } else {
            av_packet_unref(pPacket);
            av_packet_free(&pPacket);
            break;
        }
    }
    return nullptr;
}

void FFMpeg::play() {
    pthread_create(&readThreadT, nullptr, threadReadPacket, this);
    if (pAudio) {
        pAudio->play();
    }
    if (pVideo) {
        pVideo->play();
    }
}

void FFMpeg::release() {
    if (!pAudio && !pVideo) {
        return;
    }
    pthread_mutex_lock(&mutex);
    if (!pAudio->pPlayerStatus || pAudio->pPlayerStatus->isExit) {
        return;
    }
    pPlayerStatus->isExit = true;
    pthread_mutex_unlock(&mutex);
    if (readThreadT != 0) {
        pthread_join(readThreadT, nullptr);
    }
    if (pAudio) {
        pAudio->release();
        delete pAudio;
        pAudio = nullptr;
    }

    if (pVideo) {
        pVideo->release();
        delete pVideo;
        pVideo = nullptr;
    }

    if (pFormatContext) {
        avformat_close_input(&pFormatContext);
        avformat_free_context(pFormatContext);
        pFormatContext = nullptr;
    }
    pJniCall = nullptr;
    if (url) {
        free(url);
        url = nullptr;
    }
    pthread_mutex_destroy(&mutex);
    avformat_network_deinit();

    if (pPlayerStatus) {
        delete pPlayerStatus;
        pPlayerStatus = nullptr;
    }
}

void FFMpeg::prepare() {
    prepare(THREAD_MAIN);
}

void FFMpeg::callPlayJniError(ThreadMode threadMode, int code, char *msg) {
    release();
    pJniCall->callPlayError(threadMode, code, msg);
}

void *threadPrepare(void *context) {
    auto *ffMpeg = (FFMpeg *) context;
    ffMpeg->prepare(THREAD_CHILD);
    return nullptr;
}

void FFMpeg::prepareAsync() {
    pthread_t preparedThreadT;
    pthread_create(&preparedThreadT, nullptr, threadPrepare, this);
    pthread_detach(preparedThreadT);
}

void FFMpeg::prepare(ThreadMode threadMode) {
    pthread_mutex_lock(&mutex);
    av_register_all();
    avformat_network_init();

    LOGE("open url:%s", this->url);
    int formatOpenInputRes = avformat_open_input(&pFormatContext, url, nullptr, nullptr);
    if (formatOpenInputRes != 0) {
        LOGE("format open input error:%s", av_err2str(formatOpenInputRes));
        callPlayJniError(threadMode, formatOpenInputRes, av_err2str(formatOpenInputRes));
        return;
    }

    int formatFindStreamInfo = avformat_find_stream_info(pFormatContext, nullptr);
    if (formatFindStreamInfo < 0) {
        LOGE("format find stream info:%s", av_err2str(formatFindStreamInfo));
        callPlayJniError(threadMode, formatFindStreamInfo, av_err2str(formatFindStreamInfo));
        return;
    }

    int audioStreamIndex = av_find_best_stream(pFormatContext, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
    if (audioStreamIndex < 0) {
        LOGE("find audio stream index error:%d", audioStreamIndex);
        callPlayJniError(threadMode, -1, "find audio stream index error");
        return;
    }

    int videoStreamIndex = av_find_best_stream(pFormatContext, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
    if (videoStreamIndex < 0) {
        LOGE("find video stream index error:%d", videoStreamIndex);
        callPlayJniError(threadMode, -1, "find video stream index error");
    }

    pAudio = new Audio(audioStreamIndex, pJniCall, pPlayerStatus);
    pAudio->analysisStream(threadMode, pFormatContext);

    if (videoStreamIndex >= 0) {
        pVideo = new Video(videoStreamIndex, pJniCall, pPlayerStatus, pAudio);
        pVideo->analysisStream(threadMode, pFormatContext);
    }
    pJniCall->callPlayPrepared(threadMode);
    pthread_mutex_unlock(&mutex);
}

void FFMpeg::seek(uint64_t second) const {
    if (pAudio) {
        pAudio->seek(second);
    }
    if (pVideo) {
        pVideo->seek(second);
    }
}

void FFMpeg::setSurface(jobject jsurface) const {
    if (pVideo) {
        pVideo->setNativeSurface(jsurface);
    }
}
